/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.huawei.health.ecology.fa.ability;

import com.huawei.health.ecology.fa.ResourceTable;
import com.huawei.health.ecology.fa.central.client.CentralJsClient;
import com.huawei.health.ecology.fa.central.module.CentralClientModule;
import com.huawei.health.ecology.fa.utils.JsFaPage;
import com.huawei.health.ecology.fa.utils.LogUtil;
import com.huawei.health.ecology.fa.utils.ResourceUtils;
import com.huawei.healthecology.client.JsFaClient;
import com.huawei.healthecology.data.utils.ConditionOperation;
import com.huawei.healthecology.json.JsonMapperType;
import com.huawei.healthecology.module.ClientModule;

import lombok.NonNull;
import ohos.aafwk.content.Intent;
import ohos.aafwk.content.IntentParams;
import ohos.ace.ability.AceAbility;
import ohos.agp.components.AttrHelper;
import ohos.agp.components.Component;
import ohos.agp.components.DirectionalLayout;
import ohos.agp.components.LayoutScatter;
import ohos.agp.components.Text;
import ohos.agp.window.dialog.ToastDialog;
import ohos.app.Context;
import ohos.bluetooth.BluetoothHost;
import ohos.global.configuration.Configuration;
import ohos.global.resource.ResourceManager;
import ohos.net.NetManager;
import ohos.system.version.SystemVersion;

import java.util.Locale;
import java.util.Optional;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Main Ability
 */
public class MainAbility extends AceAbility {
    private static final String TAG = "CentralFaMainAbility";

    private static final String WINDOW_MODAL = "window_modal";

    private static final String IS_DARK_MODE = "isDarkMode";

    private static final String INTENT_PARAM_BACKGROUND_COLOR = "semi_modal_color";

    private static final String INTENT_PARAM_BLE_MAC = "bleMac";

    private static final String INTENT_PARAM_PRODUCT_ID = "productId";

    private static final String INTENT_PARAM_MAC_ADDRESS = "macAddress";

    private static final String INTENT_KEY_PARAMS = "params";

    private static final String INTENT_PRODUCT_INFO = "productInfo";

    private static final String INTENT_PARAM_RESTART_REQUIRED = "restartRequired";

    private static final String INTENT_PARAM_BUSINESS_INFO = "businessInfo";

    private static final String INTENT_PARAM_BUNDLE_NAME = "bundleName";

    private static final String INTENT_PARAM_ABILITY_NAME = "abilityName";

    private static final String INTENT_KEY_91 = "91";

    private static final String REGEX_REPLACE_MAC = "(.{2})";

    private static final String MAC_REPLACEMENT = ":$1";

    private static final int BINARY_COMPLEMENT = 0xff;

    private static final int VP2PX_VALUE = 64;

    private static final int DATA_BYTE_LENGTH = 2;

    private static final int WINDOW_MODAL_CARD = 3;

    private static final int WINDOW_MODAL_HALF = 1;

    private static final int PRODUCT_ID_LENGTH = 4;

    private static final int TOAST_DURATION = 3500;

    private static final int OHOS_API_5 = 5;

    private static final int DEFAULT_AUTO_TERMINATE_TIME = 1_000;

    private static final int MAC_ADDRESS_STRING_LENGTH = 17;

    private static final ScheduledExecutorService taskScheduler = Executors.newSingleThreadScheduledExecutor();

    private static final AtomicInteger loadedPageStack = new AtomicInteger(0);

    /**
     * Page Launch
     * The page is displayed based on different parameters.
     *
     * @param intent Page Transfer Parameter
     */
    @Override
    public void onStart(Intent intent) {
        if (intent == null) {
            return;
        }
        if (SystemVersion.getApiVersion() < OHOS_API_5) {
            JsFaPage.setCurrentPage(JsFaPage.HINTS_PAGE);
            intent.setParam(WINDOW_MODAL, WINDOW_MODAL_CARD);
            setInstanceName(JsFaPage.getCurrentPage());
            setPageParams(null, intent.getParams());
            super.onStart(intent);
            return;
        }

        JsFaClient.builder().abilityContext(this)
            .mapperType(JsonMapperType.GSON)
            .build();
        CentralClientModule.inject(getContext());
        CentralJsClient.builder().mapperType(JsonMapperType.GSON).build();

        Optional.ofNullable(getResourceManager())
            .map(ResourceManager::getConfiguration)
            .map(Configuration::getSystemColorMode)
            .ifPresent(colorMode -> intent.setParam(IS_DARK_MODE, colorMode == Configuration.DARK_MODE));

        if (intent.hasParameter(INTENT_PARAM_BUSINESS_INFO) || intent.hasParameter(INTENT_PARAM_BLE_MAC)) {
            // Started by NFC
            JsFaPage.setCurrentPage(JsFaPage.CONNECTION_PAGE);
            intent.setParam(WINDOW_MODAL, WINDOW_MODAL_CARD);
            setInstanceName(JsFaPage.getCurrentPage());
            setProductId(intent);
            setMacAddress(intent);
        } else if (intent.hasParameter(INTENT_KEY_PARAMS)) {
            // Started by MainAbility
            JsFaPage.setCurrentPage(JsFaPage.DASHBOARD_PAGE);
            intent.setParam(INTENT_PARAM_BACKGROUND_COLOR,
                ResourceUtils.getColor(this, ResourceTable.Color_semi_modal_background));
            intent.setParam(WINDOW_MODAL, WINDOW_MODAL_HALF);
            setInstanceName(JsFaPage.getCurrentPage());
        } else {
            // Started by debugging
            JsFaPage.setCurrentPage(JsFaPage.CONNECTION_PAGE);
            intent.setParam(WINDOW_MODAL, WINDOW_MODAL_CARD);
            setInstanceName(JsFaPage.getCurrentPage());
        }
        setBundleInfo(intent);
        if (!isBlueToothAvailable()) {
            showToast(this, ResourceUtils.getString(this, ResourceTable.String_public_bt_down));
            terminateAbility();
        }
        if (!isNetworkAvailable()) {
            showToast(this, ResourceUtils.getString(this, ResourceTable.String_public_network_down));
            terminateAbility();
        }
        setPageParams(null, intent.getParams());
        loadedPageStack.incrementAndGet();
        super.onStart(intent);
    }

    @Override
    public void onStop() {
        LogUtil.debug(TAG, "onStop");
        loadedPageStack.decrementAndGet();
        taskScheduler.schedule(() ->
            ConditionOperation.of(loadedPageStack.get() == 0).ifExist(emptyStack -> {
                ClientModule.release();
                CentralClientModule.destroy();
        }), DEFAULT_AUTO_TERMINATE_TIME, TimeUnit.MILLISECONDS);
        super.onStop();
    }

    @Override
    public void onRequestPermissionsFromUserResult(int requestCode, String[] permissions, int[] grantResults) {
        CentralClientModule.getOhosPermissionProcessor()
            .ifPresent(processor -> processor.onPermissionResult(requestCode, permissions, grantResults));
    }

    private void setBundleInfo(Intent intentVariable) {
        intentVariable.setParam(INTENT_PARAM_BUNDLE_NAME, getBundleName());
        intentVariable.setParam(INTENT_PARAM_ABILITY_NAME, getClass().getName());
    }

    /**
     * resolve the product id from the intent and put into the param of intent
     *
     * @param intentVariable The passed intent
     */
    private void setProductId(Intent intentVariable) {
        Optional.ofNullable(intentVariable)
            .map(intent -> Optional.ofNullable(intent.getStringParam(INTENT_PRODUCT_INFO))
                .map(productId -> intent.setParam(INTENT_PARAM_PRODUCT_ID, productId.substring(0, PRODUCT_ID_LENGTH))));
    }

    /**
     * resolve the Mac Address from the intent
     *
     * @param intentVariable The passed intent
     */
    private void setMacAddress(Intent intentVariable) {
        Optional.ofNullable(intentVariable)
            .filter(intent -> intent.hasParameter(INTENT_PARAM_BLE_MAC))
            .map(intent -> intent.getStringParam(INTENT_PARAM_BLE_MAC))
            .ifPresent(bleMac -> this.setMacAddress(intentVariable, bleMac));
        Optional.ofNullable(intentVariable)
            .filter(intent ->
                (!intent.hasParameter(INTENT_PARAM_BLE_MAC) && intent.hasParameter(INTENT_PARAM_BUSINESS_INFO)))
            .map(intent -> intent.getParam(INTENT_PARAM_BUSINESS_INFO))
            .filter(businessInfo -> businessInfo.hasParam(INTENT_KEY_91))
            .ifPresent(businessInfo -> this.setMacAddressByByteArray(intentVariable, businessInfo));
    }

    private void setMacAddress(Intent intentVariable, String address) {
        String macAddress = validateMacAddress(address)
            ? address
            : address.replaceAll(REGEX_REPLACE_MAC, MAC_REPLACEMENT).substring(1);
        Optional.ofNullable(intentVariable).ifPresent(
            intent -> intent.setParam(INTENT_PARAM_MAC_ADDRESS, macAddress.toUpperCase(Locale.getDefault())));
    }

    private boolean validateMacAddress(String address) {
        String macRegex = "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$";
        Pattern pattern = Pattern.compile(macRegex);
        return Optional.ofNullable(address).map(pattern::matcher).map(Matcher::matches).orElse(false);
    }

    private void setMacAddressByByteArray(Intent intent, IntentParams businessInfo) {
        if (businessInfo.getParam(INTENT_KEY_91) instanceof byte[]) {
            byte[] rawInfo = (byte[]) businessInfo.getParam(INTENT_KEY_91);
            StringBuilder stringBuilder = new StringBuilder();
            if (rawInfo != null && intent != null) {
                for (byte item : rawInfo) {
                    int intValue = item & BINARY_COMPLEMENT;
                    String stringValue = Integer.toHexString(intValue);
                    if (stringValue.length() < DATA_BYTE_LENGTH) {
                        stringBuilder.append(0);
                    }
                    stringBuilder.append(stringValue).append(":");
                }
                String macAddress = stringBuilder.substring(0, MAC_ADDRESS_STRING_LENGTH)
                    .toUpperCase(Locale.getDefault());
                LogUtil.info(TAG, "macAddress = " + macAddress);
                intent.setParam(INTENT_PARAM_MAC_ADDRESS, macAddress);
            }
        }
    }

    private void showToast(@NonNull Context context, @NonNull String message) {
        Optional.ofNullable(LayoutScatter.getInstance(this))
            .map(layoutScatter -> layoutScatter.parse(ResourceTable.Layout_toast, null, false))
            .filter(component -> component instanceof DirectionalLayout)
            .map(component -> (DirectionalLayout) component)
            .ifPresent(toast ->
                Optional.ofNullable(toast.getComponentAt(0))
                    .filter(textComponent -> textComponent instanceof Text)
                    .map(textComponent -> (Text) textComponent)
                    .ifPresent(toastText -> {
                        toastText.setText(message);
                        new ToastDialog(context).setComponent((Component) toast)
                            .setOffset(0, AttrHelper.vp2px(VP2PX_VALUE, this))
                            .setTransparent(true)
                            .setSize(DirectionalLayout.LayoutConfig.MATCH_CONTENT,
                                DirectionalLayout.LayoutConfig.MATCH_CONTENT)
                            .setDuration(TOAST_DURATION)
                            .show();
                    }));
    }

    private boolean isBlueToothAvailable() {
        return Optional.ofNullable(BluetoothHost.getDefaultHost(this))
            .map(bluetoothHost -> bluetoothHost.getBtState() == BluetoothHost.STATE_ON)
            .orElse(false);
    }

    private boolean isNetworkAvailable() {
        return Optional.ofNullable(NetManager.getInstance(this))
            .map(NetManager::getAllNets)
            .map(netHandles -> netHandles.length > 0)
            .orElse(false);
    }

    @Override
    public void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        if (intent.getBooleanParam(INTENT_PARAM_RESTART_REQUIRED, false)) {
            restart();
        }
    }
}
